package dns;
/******************************************************
 Laboratoire #3 : Programmation d'un serveur DNS
 
 Cours :             LOG610
 Session :           Hiver 2007
 Groupe :            01
 Projet :            Laboratoire #3
 ?tudiant(e)(s) :    Maxime Bouchard
 Code(s) perm. :     BOUM24028309
 
 Professeur :        Michel Lavoie 
 Nom du fichier :    UDPReceiver.java
 Date cr?e :         2007-03-10
 Date dern. modif.   X
 *******************************************************/
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketAddress;
import java.util.ArrayList;


/**
 * Cette classe permet la r?ception d'un paquet UDP sur le port de r?ception
 * UDP/DNS. Elle analyse le paquet et extrait le hostname
 * 
 * Il s'agit d'un Thread qui ?coute en permanance 
 * pour ne pas affecter le d?roulement du programme
 * @author Max
 *
 */


public class UDPReceiver extends Thread {
	/**
	 * Les champs d'un Packet UDP
	 * --------------------------
	 * En-t?te (12 octects)
	 * Question : l'adresse demand?
	 * R?ponse : l'adresse IP
	 * Autorit? : info sur le serveur d'autorit?
	 * Additionnel : information suppl?mentaire
	 */
	
	/**
	 * D?finition de l'En-t?te d'un Packet UDP
	 * ---------------------------------------
	 * Identifiant Param?tres
	 * QDcount Ancount
	 * NScount ARcount
	 * 
	 *? identifiant est un entier permettant d?identifier la requete.
	 *? parametres contient les champs suivant :
	 *	? QR (1 bit) : indique si le message est une question (0) ou une reponse (1).
	 *	? OPCODE (4 bits) : type de la requete (0000 pour une requete simple).
	 *	? AA (1 bit) : le serveur qui a fourni la reponse a-t?il autorite sur le domaine?
	 *	? TC (1 bit) : indique si le message est tronque.
	 *	? RD (1 bit) : demande d?une requete recursive.
	 *	? RA (1 bit) : indique que le serveur peut faire une demande recursive.
	 *	? UNUSED, AD, CD (1 bit chacun) : non utilises.
	 *	? RCODE (4 bits) : code de retour. 0 : OK, 1 : erreur sur le format de la requete, 2: probleme du serveur,
	 *    3 : nom de domaine non trouve (valide seulement si AA), 4 : requete non supportee, 5 : le serveur refuse
	 *    de repondre (raisons de s?ecurite ou autres).
	 * ? QDCount : nombre de questions.
	 * ? ANCount, NSCount, ARCount : nombre d?entrees dans les champs ?Reponse?, ?Autorite?, ?Additionnel?.
	 */
	
	/**
	 * Les champs Reponse, Autorite, Additionnel sont tous representes de la meme maniere :
	 *
	 * ? Nom (16 bits) : Pour eviter de recopier la totalite du nom, on utilise des offsets. Par exemple si ce champ
	 *   vaut C0 0C, cela signifie qu?on a un offset (C0) de 12 (0C) octets. C?est-a-dire que le nom en clair se trouve
	 *   au 12eme octet du message.
	 * ? Type (16 bits) : idem que pour le champ Question.
	 * ? Class (16 bits) : idem que pour le champ Question.
	 * ? TTL (32 bits) : dur?ee de vie de l?entr?ee.
	 * ? RDLength (16 bits): nombre d?octets de la zone RDData.
	 * ? RDData (RDLength octets) : reponse
	 */
	
	
	protected final static int BUF_SIZE = 1024;
	protected String SERVER_DNS = null;
	protected int port = 53;  // port de r?ception
	private String DomainName = "none";
	private String DNSFile = "DNSFILE.TXT";
	private String adrIP = null;
	private boolean RedirectionSeulement = false;
	private String adresseIP = null;
	private byte[] receiveData = new byte[BUF_SIZE];
	private ArrayList<Integer> colBytes = null;
	private DataInputStream d = null;
	private String id = null;
	
	public void setport(int p) {
		this.port = p;
	}
	
	public void setRedirectionSeulement(boolean b){
		this.RedirectionSeulement = b;
	}
	
	public String gethostNameFromPacket(){
		return DomainName;
	}
	
	public String getAdrIP(){
		return adrIP;
	}
	
	private void setAdrIP(String ip){
		adrIP = ip;
	}
	
	public void sethostNameFromPacket(String hostname){
		this.DomainName = hostname;
	}
	
	public String getSERVER_DNS(){
		return SERVER_DNS;
	}
	
	public void setSERVER_DNS(String server_dns){
		this.SERVER_DNS = server_dns;
	}
	
	public void UDPReceiver(String server_dns,int Port) {
		this.SERVER_DNS = server_dns;
		this.port = Port;
	}
	
	public void setDNSFile(String filename){
		DNSFile = filename;
	}
	
	public void run(){

		try{
			
			//*Creation d'un socket UDP
			DatagramSocket ds = new DatagramSocket(53);
			
			//*Boucle infinie de recpetion
			while(true){
				
				//*Reception d'un paquet UDP via le socket
				DatagramPacket packet = new DatagramPacket(receiveData,receiveData.length);
				ds.receive(packet);
				
				//*Creation d'un DataInputStream ou ByteArrayInputStream pour manipuler les bytes du paquet				
				ByteArrayInputStream bis = new ByteArrayInputStream(packet.getData());
				generateByteCollection(bis);
				
				//*Lecture et sauvegarde des deux premier bytes, qui specifie l'identifiant
				//*Lecture et sauvegarde du huitieme byte, qui specifie le nombre de reponse dans le message 

				
				//*Dans le cas d'une reponse
				if (colBytes.get(7) == 1 ){

					//*Lecture du Query Domain name, a partir du 13 byte
					//*Sauvegarde du Query Domain name
					DomainName = domainNameBuilder();
					
					//*Passe par dessus Query Type et Query Class
					//*Passe par dessus les premiers champs du ressource record pour arriver au ressource data
					//*qui contient l'adresse IP associe au hostname (dans le fond saut de 16 bytes)
					adrIP = ipAddressBuilder();
					
					//*Capture de l'adresse IP
					//*Ajouter la correspondance dans le fichier seulement si une seule
					//*reponse dans le message DNS (cette apllication ne traite que ce cas)
					
					AnswerRecorder answerR = new AnswerRecorder(DNSFile);
					answerR.StartRecord(DomainName, adrIP);
					
					//*Faire parvenir le paquet reponse au demandeur original, ayant emis une requete 
					//*avec cet identifiant
					DatagramPacket clientPacket = new DatagramPacket(packet.getData(), packet.getLength(), InetAddress.getLocalHost(), port);
					ds.send(clientPacket);
					

				//*Dans le cas d'une requete
				}else{
					//*Lecture du Query Domain name, a partir du 13 byte
					
					//*Sauvegarde du Query Domain name
					DomainName = domainNameBuilder();
					
					//*Sauvegarde de l'adresse, du port et de l'identifiant de la requete
					adrIP = packet.getAddress().toString();
					id = colBytes.get(0)+""+colBytes.get(1);

					//*Si le mode est redirection seulement
					if(RedirectionSeulement == true){
						//*Rediriger le paquet vers le serveur DNS
					
					//*Sinon
					}else{
						//*Rechercher l'adresse IP associe au Query Domain name dans le fichier de 
						//*correspondance de ce serveur
						QueryFinder qf = new QueryFinder(DNSFile);
					
						//*Si la correspondance n'est pas trouvee
						if (qf.StartResearch(DomainName).equals("none")){
							//*Rediriger le paquet vers le serveur DNS
							DatagramPacket rPacket = new DatagramPacket(packet.getData(), packet.getLength(), InetAddress.getByName(SERVER_DNS), 53);
							ds.send(rPacket);
							
						}else{
						//*Sinon
							
							//*Creer le paquet de reponse a l'aide du UDPAnswerPaquetCreator
							//*Placer ce paquet dans le socket
							//*Envoyer le paquet

							UDPAnswerPacketCreator udpPacket = new UDPAnswerPacketCreator();
							byte[] p = udpPacket.CreateAnswerPacket(packet.getData(), qf.StartResearch(DomainName));
							//DatagramPacket newPacket = new DatagramPacket(receiveData,receiveData.length);
							packet.setData(p);
							ds.send(packet);	
						}
					}
				}
			}
		}catch(Exception e){
			System.err.println("Probl?me ? l'ex?cution :");
			e.printStackTrace(System.err);
		}	
	}
	
	private String ipAddressBuilder(){

		int pointer = 12;
		
		while (colBytes.get(pointer) != 0){
			pointer++;
		}
		
		pointer += 17;
		
		
		return (colBytes.get(pointer) +"."+ colBytes.get(pointer+1) +"."+ colBytes.get(pointer+2) + "." + colBytes.get(pointer+3));
	}
	
	private  String domainNameBuilder(){
		
		StringBuilder domain = new StringBuilder();
		int pointer = 12;
		
		while(colBytes.get(pointer) != 0){
			
			int delta = colBytes.get(pointer);
			for (int i = pointer + 1; i < delta + pointer + 1; i++){
				domain.append(Character.toChars(colBytes.get(i)));
			}
			
			pointer += delta + 1;
			
			if(colBytes.get(pointer) != 0)
				domain.append(".");
		}
		
		return domain.toString();
	}
	
	private void generateByteCollection(ByteArrayInputStream bis){

		colBytes = new ArrayList<Integer>();
		int b = 0;
		while ((b = bis.read()) != -1){
			colBytes.add(b);
		}
	}
}
